%%%-------------------------------------------------------------------
%%% @author liuwentao
%%% @doc
%%%     角色定时器
%%%     单个游戏节点 开定时器的数量是有限的 所以每个角色只开一个定时器
%%% @end
%%% Created : 15. 7月 2021 15:39
%%%-------------------------------------------------------------------
-module(role_timer).
-author("liuwentao").

-export([
    add/2
    , add/5
    , del/2
    , set/2
    , tick/1
    , set_ms_timer/3
    , set_loop_ms_timer/4
    , unset_timer/1
    , unset_timers/1
    , get_timer/2

]
).
-include("common.hrl").
-include("role.hrl").
-include("timer.hrl").

%% ----------------------------------------------------
%% 外部接口
%% ----------------------------------------------------

%% @doc 添加定时器
-spec add(TimerId :: term(), TimeOut :: non_neg_integer(), MFA :: tuple(), Times :: integer(), Role :: #role{}) -> NewRole :: #role{}.
add(TimerId, TimeOut, {M, F, A}, Times, Role) ->
    Timer = #timer{
        id = TimerId,
        timeout = TimeOut,
        m = M,
        f = F,
        a = A,
        times = Times
    },
    add(Timer, Role).

%% @doc 添加定时器
-spec add(#timer{}, #role{}) -> #role{}.
add(Timer, Role = #role{timer = #rtimer{ref = Ref, timers = Timers}}) ->
    catch erlang:cancel_timer(Ref),
    next([Timer | Timers], Role).

%% @doc 重置指定定时器
-spec set(#timer{}, #role{}) -> #role{}.
set(Timer = #timer{id = Id}, Role) ->
    Role0 = del(Id, Role),
    add(Timer, Role0).

%% @doc 删除定时器
-spec del(pos_integer(), #role{}) -> #role{}.
del(_TimerId, Role = #role{timer = #rtimer{timers = []}}) -> Role;
del(TimerId, Role = #role{timer = #rtimer{ref = Ref, timers = [#timer{id = TimerId} | Timers]}}) ->
    %% 删除正在计时状态中的定时器
    erlang:cancel_timer(Ref),
    next(Timers, Role);
del(TimerId, Role = #role{timer = Rtimer = #rtimer{timers = Timers}}) ->
    %% 删除非计时状态中的定时器
    Role#role{timer = Rtimer#rtimer{timers = lists:keydelete(TimerId, #timer.id, Timers)}}.

%% @doc 触发定时器
-spec tick(#role{}) -> #role{}.
tick(Role = #role{timer = #rtimer{timers = []}}) -> Role;
tick(Role = #role{name = Name, timer = #rtimer{timers = [Timer = #timer{id = Id, times = Times, m = M, f = F, a = A} | Timers]}}) ->
    NRole = next([Timer#timer{times = max(-1, Times - 1), next_tick = 0} | Timers], Role),
    Rtn = case M of
              undefined -> catch erlang:apply(F, [NRole | A]);
              _ -> catch erlang:apply(M, F, [NRole | A])
          end,
    case Rtn of
        keep when Times =:= 1 ->
            set(Timer#timer{times = 1, next_tick = 0}, NRole);
        {keep, NewRole} when is_record(NewRole, role) andalso Times =:= 1 ->
            set(Timer#timer{times = 1, next_tick = 0}, NewRole);
        remove when Times =/= 1 ->
            del(Id, NRole);
        {remove, NewRole} when is_record(NewRole, role) andalso Times =/= 1 ->
            del(Id, NewRole);
        Atom when Atom =:= ok orelse Atom =:= keep orelse Atom =:= remove ->
            NRole;
        {Atom, NewRole = #role{}} when Atom =:= ok orelse Atom =:= keep orelse Atom =:= remove ->
            NewRole;
        Else ->
            ?ERR("角色[~ts]执行定时器{~w}时得到错误的返回值格式: ~w", [Name, Timer, Else]),
            NRole
    end.

%% ----------------------------------------------------
%% 私有函数
%% ----------------------------------------------------

%% 设置下一个定时器
next([], Role) -> %% 最后一个定时器失效
    Role#role{timer = #rtimer{}};
next([#timer{times = 0} | Timers], Role) ->
    next(Timers, Role);
next([Timer = #timer{next_tick = 0, timeout = To} | Timers], Role) ->
    Now = date:unixtime(ms),
    NewTimer = Timer#timer{set_time = Now, next_tick = Now + To},
    next([NewTimer | Timers], Role);
next(Timers, Role) ->
    Now = date:unixtime(ms),
    L = lists:keysort(#timer.next_tick, Timers),
    [#timer{id = _Id, next_tick = Nt} | _] = L,
    T = case Nt - Now of
            Diff when Diff > 0 -> Diff;
            _ -> 100
        end,
    Key = erlang:make_ref(),
    NewRef = erlang:send_after(T, self(), {timer_tick, Key}),
    Role#role{timer = #rtimer{ref = NewRef, key = Key, timers = L}}.

%% @doc 设置timer只能在role进程内部使用
%% Timeout毫秒
set_ms_timer(Name, Timeout, {Fun}) when is_function(Fun) ->
    util:set_ms_timer(Name, Timeout, {apply_async, {Fun}}),
    ok;
set_ms_timer(Name, Timeout, {Fun, A}) when is_function(Fun) ->
    util:set_ms_timer(Name, Timeout, {apply_async, {Fun, A}}),
    ok;
set_ms_timer(Name, Timeout, {M, F, A}) ->
    util:set_ms_timer(Name, Timeout, {apply_async, {M, F, A}}),
    ok;
set_ms_timer(Name, Timeout, {info, Mod, Event, Arg}) ->
    util:set_ms_timer(Name, Timeout, {reply, Mod, Event, Arg}),
    ok.

%% @doc 设置循环timer只能在role进程内部使用
set_loop_ms_timer(Name, Timeout, Count, Mix) when is_integer(Count) ->
    util:set_ms_timer(Name, Timeout, {loop_timer, {Name, Timeout, Count}, Mix}),
    ok.

%% @spec unset_timer(Name) -> void()
%% @doc 清除timer只能在role进程内部使用
unset_timer(Name) ->
    util:unset_timer(Name),
    ok.

%% @spec unset_timers(Names) -> void()
%% Names = [term()]
%% @doc 清除多个定时器只能在role进程内部使用
unset_timers(Names) ->
    util:unset_timers(Names),
    ok.

%% 获取到定时器
get_timer(#role{timer = #rtimer{timers = L}}, TimeId) ->
    case lists:keyfind(TimeId, #timer.id, L) of
        false ->
            false;
        Timer ->
            Timer
    end.